package com.lifesting.tool.j2as3.action;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.IConsoleView;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;

import com.lifesting.tool.j2as3.visitor.AnalysisVisitor;
import com.lifesting.tool.j2as3.visitor.AnalysisVisitor.ImportDescriptor;


public class AnalysisAction implements IObjectActionDelegate {

	private IWorkbenchPart part;
	private IJavaProject project;
	private final ASTParser parser;
	private final AnalysisVisitor visitor;
	public static final String CONSOLE_NAME="Java2AS3";
	
	public AnalysisAction() {
		parser = ASTParser.newParser(AST.JLS3);
		visitor = new AnalysisVisitor();
	}

	@Override
	public void setActivePart(IAction action, IWorkbenchPart targetPart) {
		this.part = targetPart;
	}

	@Override
	public void run(IAction action) {
		if (project != null){
			try {
				part.getSite().getPage().getWorkbenchWindow().getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress(){

					@Override
					public void run(IProgressMonitor monitor)
							throws InvocationTargetException, InterruptedException {
						monitor.beginTask("Analyzing Project "+project.getElementName(), IProgressMonitor.UNKNOWN);
						IPackageFragmentRoot[] all_roots;
						List<IPackageFragment> fragments = new ArrayList<IPackageFragment>();
						List<String> pkg_filters = new ArrayList<String>();
						try {
							all_roots = project.getAllPackageFragmentRoots();
							for (IPackageFragmentRoot pfr : all_roots){
								if (monitor.isCanceled()) return;
								if (pfr.getKind() == IPackageFragmentRoot.K_SOURCE){
									IJavaElement[] pfs = pfr.getChildren();
									for(IJavaElement je : pfs){
										if (monitor.isCanceled()) return;
										if (je instanceof IPackageFragment){
											IPackageFragment a_pr = (IPackageFragment) je;
											if (a_pr.getChildren().length > 0)
												fragments.add(a_pr);
										}
									}
								}
							}
						
							/*
							 * remove these package-fragments who lives in another one, 
							 * for example, there exist two package fragments a.b and a.b.c, 
							 * we will choose a.b as a package-fragment filter when analyzing. 
							 * 
							 */
							
							for (IPackageFragment pft: fragments){
								String pkgName = pft.getElementName();
								boolean found_parent = false;
								for (Iterator<String> iter = pkg_filters.iterator(); iter.hasNext();){
									String iter_name = iter.next();
									if (iter_name.startsWith(pkgName)){ //a.b.c => a.b 
										iter.remove();
									}else{ //pkgName.startWith(iter_name); 
										if (!found_parent) found_parent = true;
									}
								}
								if (!found_parent)
									pkg_filters.add(pkgName);
							}
							visitor.setNoNeedCount(pkg_filters.toArray(new String[pkg_filters.size()]));
							
							for (IPackageFragment pf : fragments){
								if (monitor.isCanceled()) return;
								ICompilationUnit[] units = pf.getCompilationUnits();
								for (ICompilationUnit u : units){
									if (monitor.isCanceled()) return;
									monitor.subTask("Analyzing file "+u.getUnderlyingResource().getFullPath().toString());
									parser.setSource(u);
									parser.setResolveBindings(true);
									ASTNode root_node = parser.createAST(monitor);
									root_node.accept(visitor);
								}
							}
							part.getSite().getShell().getDisplay().syncExec(new Runnable(){
								@Override
								public void run() {
									showDependence(visitor.getAllOuterDefinitions());
								}
							});
							
							monitor.done();
						} catch (JavaModelException e) {
							e.printStackTrace();
							throw new InvocationTargetException(e);
						}
						finally{
							visitor.reset();
						}
						
						
					}
					
				});
			} catch (Exception e){
				dialogShowException(e);
			}
		}
	}
	public void showConstructor(HashMap<String, Integer> states){
		MessageConsole console = getAnalysisConsole();
		MessageConsoleStream stream = console.newMessageStream();
		stream.println("Constructor number exceeds 1");
		for (Iterator<String> iter = states.keySet().iterator(); iter.hasNext();){
			String type_name = iter.next();
			stream.println("\t"+type_name+"("+states.get(type_name)+")");
		}
	}
	protected void showDependence(ImportDescriptor[] entries) {
		MessageConsole console = getAnalysisConsole();
		try{
			showConsole(console);
			MessageConsoleStream stream = console.newMessageStream();
			tableStyleShow(stream,entries);
		}catch(Exception e){
			dialogShowException(e);
		}
	}

	private void tableStyleShow(MessageConsoleStream stream, ImportDescriptor[] entries) {
		stream.println("Result for project "+project.getElementName());
		int max_cn_len = 0;
		Color red = part.getSite().getShell().getDisplay().getSystemColor(SWT.COLOR_RED);
		Color def_color = stream.getColor();
		for (ImportDescriptor d : entries){
			String cls_name = d.getClassName();
			if (cls_name.length() > max_cn_len) max_cn_len = cls_name.length();
		}
		max_cn_len += 4;
		for (ImportDescriptor d: entries){
			String cls_name = d.getClassName();
			int space = max_cn_len - cls_name.length();
			stream.setColor(red);
			stream.print(cls_name);
			stream.setColor(def_color);
			stream.print(new String(getSpaces(space)));
			stream.print(String.valueOf(d.getImported()));
			stream.print("\t\t");
			stream.println();
			for (String file : d.getJavaFiles()){
				stream.println("\t"+file);
			}
			stream.println();
		}
		stream.setColor(def_color);
	}

	private char[] getSpaces(int space) {
		char[] ss = new char[space];
		Arrays.fill(ss,' ');
		return ss;
	}

	private void dialogShowException(final Exception e){
		part.getSite().getShell().getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				MessageDialog.openError(part.getSite().getShell(), "Exception", e.getLocalizedMessage());
			}
		});
	}
	private boolean showConsole(final MessageConsole console) {
		final int[] status = {0};
		try {
			part.getSite().getPage().getWorkbenchWindow().run(false, false, new IRunnableWithProgress() {
				
				@Override
				public void run(IProgressMonitor monitor) throws InvocationTargetException,
						InterruptedException {
					IWorkbenchPage page = part.getSite().getPage();
					IConsoleView console_view;
					try {
						if ((console_view = (IConsoleView) page.showView(IConsoleConstants.ID_CONSOLE_VIEW)) != null){
							console_view.display(console);
						}
					} catch (PartInitException e) {
						status[0] = 1;
						dialogShowException(e);
					}									
				}
			});
		} catch (Exception e){
			status[0] = 1;
		}
		return status[0] == 0;
	}

	static MessageConsole getAnalysisConsole() {
		 ConsolePlugin plugin = ConsolePlugin.getDefault();
	      IConsoleManager conMan = plugin.getConsoleManager();
	      IConsole[] existing = conMan.getConsoles();
	      for (int i = 0; i < existing.length; i++)
	         if (CONSOLE_NAME.equals(existing[i].getName()))
	            return (MessageConsole) existing[i];
	      MessageConsole myConsole = new MessageConsole(CONSOLE_NAME, null);
	      conMan.addConsoles(new IConsole[]{myConsole});
	      return myConsole;
	}

	@Override
	public void selectionChanged(IAction action, ISelection selection) {
		project = null;
		Object selected = ((StructuredSelection)selection).getFirstElement();
		if(selected instanceof IJavaProject){
			project = (IJavaProject) selected;
		}
		else if (selected instanceof IAdaptable){
			Object obj = ((IAdaptable)selected).getAdapter(IJavaElement.class);
			if (obj instanceof IJavaProject)
				project = (IJavaProject) obj;
		}
	}

}
